/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sg.edu.nus.iss.pacman.game;

import java.util.Vector;

/**
 * An AI for the Ghosts based on A* path finding.
 * @author fredy, tabiul.m
 */
public class GhostAI
{
    private GhostAI()
    {
        // Prevent instantiation.
    }

    /**
     * Gets the next position for the ghost.
     * @param pacMan the PacMan
     * @param ghost the ghost
     * @param maze the maze
     * @return the next postion
     */
    public static int getGhostNextPosition(Player pacMan, Player ghost,
        int[][] maze)
    {
        Node startNode = new Node();
        Node targetNode = new Node();
        startNode.setColumn(ghost.getCurrentColumn());
        startNode.setRow(ghost.getCurrentRow());

        targetNode.setColumn(pacMan.getCurrentColumn());
        targetNode.setRow(pacMan.getCurrentRow());

        int position = calculate(startNode, targetNode, maze);
        return position;
    }

    private static int calculate(Node startNode, Node targetNode, int[][] maze)
    {
        Vector openNodeList = new Vector();
        Vector closeNodeList = new Vector();
        openNodeList.addElement(startNode);
        while (openNodeList.size() > 0 && !closeNodeList.contains(targetNode))
        {
            Node currentNode = getLowestDistanceNode(openNodeList);
            closeNodeList.addElement(currentNode);
            openNodeList.removeElement(currentNode);

            Vector adjacentNodes = getAdjacentNodes(currentNode.getColumn(),
                currentNode.getRow(), maze);
            for (int i = 0; i < adjacentNodes.size(); i++)
            {
                Node adjacentNode = (Node) adjacentNodes.elementAt(i);
                if (!closeNodeList.contains(adjacentNode))
                {
                    adjacentNode.setParentColumn(currentNode.getColumn());
                    adjacentNode.setParentRow(currentNode.getRow());
                    if (!openNodeList.contains(adjacentNode)) // Open list
                    {
                        adjacentNode.setDistance(calculateDistance(
                            adjacentNode.getColumn(), adjacentNode.getRow(),
                            targetNode.getColumn(), targetNode.getRow()));
                        openNodeList.addElement(adjacentNode);
                    }
                }
            }
        }
        return getNextPosition(startNode, getNextMove(startNode, targetNode, closeNodeList));
    }

    private static int getNextPosition(Node startNode, Node nextNode)
    {
        int position = 0;
        if (nextNode != null)
        {
            if (startNode.getColumn() > nextNode.getColumn())
            {
                position = Player.LEFT;
            }
            else if (startNode.getColumn() < nextNode.getColumn())
            {
                position = Player.RIGHT;
            }
            else if (startNode.getRow() > nextNode.getRow())
            {
                position = Player.UP;
            }
            else if (startNode.getRow() < nextNode.getRow())
            {
                position = Player.DOWN;
            }
        }
        return position;
    }

    private static Node getNextMove(Node startNode, Node targetNode, Vector closeNodeList)
    {
        int column = targetNode.getColumn();
        int row = targetNode.getRow();
        Node node = null;
        while ((node = findNode(column, row, closeNodeList)) != null)
        {
            if (node.getParentColumn() == startNode.getColumn() &&
                node.getParentRow() == startNode.getRow())
            {
                break;
            }
            else
            {
                column = node.getParentColumn();
                row = node.getParentRow();
            }
        }
        return node;
    }

    private static Node findNode(int column, int row, Vector closeNodeList)
    {
        for (int i = 0; i < closeNodeList.size(); i++)
        {
            Node node = (Node) closeNodeList.elementAt(i);
            if (node.getColumn() == column && node.getRow() == row)
            {
                return node;
            }
        }
        return null;
    }

    private static Vector getAdjacentNodes(int column, int row, int[][] maze)
    {
        Vector nodeList = new Vector();
        if (!isBlockedTerrain(column + 1, row, maze))
        {
            Node node = new Node();
            node.setColumn(column + 1);
            node.setRow(row);
            nodeList.addElement(node);
        }

        if (!isBlockedTerrain(column - 1, row, maze))
        {
            Node node = new Node();
            node.setColumn(column - 1);
            node.setRow(row);
            nodeList.addElement(node);
        }

        if (!isBlockedTerrain(column, row + 1, maze))
        {
            Node node = new Node();
            node.setColumn(column);
            node.setRow(row + 1);
            nodeList.addElement(node);
        }

        if (!isBlockedTerrain(column, row - 1, maze))
        {
            Node node = new Node();
            node.setColumn(column);
            node.setRow(row - 1);
            nodeList.addElement(node);
        }
        return nodeList;
    }

    private static boolean isBlockedTerrain(int column, int row, int[][] maze)
    {
        boolean blocked = false;
        try
        {
            if (maze[row][column] == NodeType.WALL)
            {
                blocked = true;
            }
        }
        catch (Exception ex)
        {
            // Do nothing.
        }
        return blocked;
    }

    private static int calculateDistance(int srcColumn, int srcRow, int destColumn, int destRow)
    {
        return (Math.abs(srcColumn - destColumn) * 10) + (Math.abs(srcRow - destRow) * 10);
    }

    private static Node getLowestDistanceNode(Vector openNodeList)
    {
        Node lowestDistanceNode = null;
        if (openNodeList.size() > 0)
        {
            lowestDistanceNode = (Node) openNodeList.elementAt(0);
            for (int i = 1; i < openNodeList.size(); i++)
            {
                Node n = (Node) openNodeList.elementAt(i);
                if (n.getDistance() < lowestDistanceNode.getDistance())
                {
                    lowestDistanceNode = n;
                }
            }
        }
        return lowestDistanceNode;
    }
}
